Unity - Android Share Texture
需求来源
原始的需求是打算在Unity中申请一个Texture,但是Texture的source部分是由Android提供的。
应用的场景:在Unity中显示一个Android App的画面。
技术点
- [x] Unity & Android Share Texture
- [x] Android VirtualDisplay
- [x] OpenGLES
- [x] FBO
Pipeline 流程图
sequenceDiagram # init flow Note left of Unity: 1.Init流程 Unity->>AndroidPlugin:initVirtualDisplay(unityTextureId) activate AndroidPlugin activate AndroidPlugin AndroidPlugin->>AndroidPlugin:createOESTexture deactivate AndroidPlugin Note right of AndroidPlugin: 创建OESTexture AndroidPlugin->>SurfaceTexture:new SurfaceTexture(OESTextureId) activate SurfaceTexture Note right of SurfaceTexture: 通过OESTexture创建SurfaceTexture SurfaceTexture-->>AndroidPlugin:mSurfaceTexture deactivate SurfaceTexture AndroidPlugin->>Surface:new Surface(mSurfaceTexture) activate Surface Note right of Surface: 通过SurfaceTexture创建Surface Surface-->>AndroidPlugin:mSurface deactivate Surface AndroidPlugin ->> FBOHelper: new FBO(unityTextureId,OESTextureId) activate FBOHelper Note right of FBOHelper: 构造FBOHelper类 activate FBOHelper FBOHelper ->> FBOHelper: genFBO deactivate FBOHelper FBOHelper -->> AndroidPlugin: mFBOHelper deactivate FBOHelper AndroidPlugin ->> VirtualDisplay:createVirtualDisplay(mSurface) activate VirtualDisplay Note right of VirtualDisplay: 通过mSruface创建出VirtualDisplay VirtualDisplay -->> AndroidPlugin:mVirtualDisplay deactivate VirtualDisplay AndroidPlugin-->>Unity: done with initVirtualDisplay deactivate AndroidPlugin Note left of Unity: 完成VirtualDisplay初始化 # VirtualDisplay updated Unity -->> VirtualDisplay: Update VirtualDisplay Note left of Unity: 2.VirtualDisplay画面更新流程 Unity ->> App: startActivity(display id) activate Unity activate App App->>App: SurfaceControl.setLayerStack(virtual display id) App -->> Unity: end startActivity deactivate App deactivate Unity Note right of App: App画面有更新 App->>App: invalidate() App-->>VirtualDisplay: VirtualDisplay ->> Surface : onFrameAvailable activate Surface Note right of Surface: VirtualDisplay画面有更新时 App->>App: ...... App-->>VirtualDisplay: Note right of Surface:实际Surface数据会刷新 Surface -->> VirtualDisplay : deactivate Surface App->>App: invalidate() App-->>VirtualDisplay: VirtualDisplay ->> Surface : onFrameAvailable activate Surface Surface -->> VirtualDisplay : deactivate Surface VirtualDisplay ->> Surface : onFrameAvailable activate Surface Surface -->> VirtualDisplay : deactivate Surface App->>App: invalidate() App-->>VirtualDisplay: Note right of Surface: VirtualDisplay的更新如上述循环 Note left of Unity: VirtualDisplay画面更新流程 Unity --> VirtualDisplay: # draw Note left of Unity: 3.Unity画面更新流程 Unity->>AndroidPlugin:isFrameUpdated() activate AndroidPlugin AndroidPlugin-->>Unity:return True deactivate AndroidPlugin Unity->>AndroidPlugin:draw() activate AndroidPlugin AndroidPlugin ->> SurfaceTexture: updateTexImage activate SurfaceTexture SurfaceTexture -->> AndroidPlugin: deactivate SurfaceTexture Note left of SurfaceTexture: 更新OESTexture AndroidPlugin ->> FBOHelper: draw() activate FBOHelper activate FBOHelper FBOHelper -> FBOHelper: glBindFramebuffer Note right of FBOHelper: 激活FBO deactivate FBOHelper activate FBOHelper FBOHelper -> FBOHelper:glFramebufferTexture2D Note right of FBOHelper: 绑定UnityTexture到FBO deactivate FBOHelper activate FBOHelper FBOHelper -> FBOHelper:glBindTexture Note right of FBOHelper: 绑定OESTexture到当前激活纹理单元,进行绘制 deactivate FBOHelper FBOHelper-->>AndroidPlugin:done draw() deactivate FBOHelper Note right of AndroidPlugin: 完成了OESTexutre对2DTexture的输出,更新了画面 AndroidPlugin-->>Unity:done draw() deactivate AndroidPlugin Note left of Unity: Unity画面更新完成
流程描述
- 把Android端创建的VirtualDisplay上的内容绘制到这块纹理上
- VirtualDisplay内容的绘制流程:
- VirtualDisplay画面的改变会刷新到绑定的Surface上
- VirtualDisplay -> Surface(CPU端数据处理)
- 更新Surface绑定的SurfaceTexture对象的纹理
- Surface -> SurfaceTexture(CPU内存数据更新到GPU端)
- SurfaceTexture.updateTexImage方法
- Surface -> SurfaceTexture(CPU内存数据更新到GPU端)
- SurfaceTexture绑定的纹理是GL_TEXTURE_EXTERNAL_OES
- 使用FBO(Frame Buffer Object)关联Unity的GL_TEXTURE_2D
- 把GL_TEXTURE_EXTERNAL_OES的内容绘制到FBO
- SurfaceTexture -> FBO(GPU端数据处理)
- 最终GL_TEXTURE_EXTERNAL_OES的内容就同步到了GL_TEXTURE_2D
- FBO -> GL_TEXTURE_2D(GPU端数据处理)
- VirtualDisplay画面的改变会刷新到绑定的Surface上
代码实现
根据流程图的实现,整个代码的解析可以分成4个部分:
- 创建OESTexture
- 根据OESTexture创建SurfaceTexture
- 构建FBOHelper类,关联Unity 2DTexture
- 绘制流程Draw
创建OESTexture
|
|
创建SurfaceTexture
|
|
其中可以特别参考一下SurfaceTexture
的开头注释,其中有两个点需要注意的:
第一个点是
SurfaceTexture
用到的是OESTexture,在使用Shader的时候需要额外声明:"#extension GL_OES_EGL_image_external : require"
The texture object uses the GL_TEXTURE_EXTERNAL_OES texture target, which is defined by the GL_OES_EGL_image_external OpenGL ES extension. This limits how the texture may be used. Each time the texture is bound it must be bound to the GL_TEXTURE_EXTERNAL_OES target rather than the GL_TEXTURE_2D target. Additionally, any OpenGL ES 2.0 shader that samples from the texture must declare its use of this extension using, for example, an “#extension GL_OES_EGL_image_external : require” directive. Such shaders must also access the texture using the samplerExternalOES GLSL sampler type.
第二个点是对应的OESTexture的更新是通过调用updateTexImage来触发的
When updateTexImage is called, the contents of the texture object specified when the SurfaceTexture was created are updated to contain the most recent image from the imagestream. This may cause some frames of the stream to be skipped.
构建FBOHelper类
|
|
整个程序完成了:
- 保存Unity创建的Texture
- 保存了OESTexture
- 创建了FBO
- 创建了VBO
- 创建了Fragment Shader和VertexShader
- 获取了Shader中的参数
绘制流程Draw
上述的种种都是为了在最后进行绘制,当有数据来临的时候,根据之前对SurfaceTexture的理解,我们需要完成最后的OES->2DTexture的数据填充。
|
|
- 实际使用中,这一段完成的事情为
- 启用FBO,绑定unity texture到FBO。
- 把OESTexture采样到FBO。
- 关闭FBO,完成所有的操作。
小结
整个过程其实是OESTexture -> 2DTexture的一次采样(复制),其中Surface的数据是填充在内存的,而OESTexture往2DTexture的采样是由GPU完成的。
坑点和难点:
出现了闪屏的问题:
- 12//激活一个纹理工作区域?:GLES30.GL_TEXTURE0GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
刚开始的时候出现了闪屏的问题,检查下来发现可能在Unity侧激活的纹理工作区也是TEXTURE0,这样会导致两边同时用到这块工作区域,发生了闪屏。
OESTexture属于EXT的范畴,因此在fragment shader中需要标识:
"#extension GL_OES_EGL_image_external : require"
Unity 2DTexture为什么不能直接使用?
参考
SurfaceTexture
的注释:The texture object uses the GL_TEXTURE_EXTERNAL_OES texture target
额外收获
dump texture
在调试的过程中,使用renderDoc
以及高通的SnapDragon Profiler
都出现了kernel panic的问题,远程办公的情况下需要call help让同事去工位上长按电源键重启手机,所以比较无奈只能去尝试软件dump texture,好在最后是找到了。
|
|
RenderDoc
实际使用中发现RenderDoc也是比较好用的:https://renderdoc.org/
后续做GPU相关工作的时候也可以用上,高通的平台虽然高端,但是也不能老是依赖于高通的ToolChain。